/**
* getDefinitionNames by Denis Kolyako. August 13, 2008. Updated September 16, 2009.
* Visit http://etcs.ru for documentation, updates and more free code.
*
* You may distribute this class freely, provided it is not modified in any way (including
* removing this header or changing the package path).
* 
*
* Please contact etc[at]mail.ru prior to distributing modified versions of this class.
*/
package ru.goodcore.utils {
	import flash.display.LoaderInfo;
	import flash.utils.ByteArray;
	
	/**
	 * getDefinitionNames function
	 * 
	 * @author					etc
	 * @version					2.0
	 * @playerversion			Flash 9.0.45+
	 * @langversion				3.0
	 */
	/**
	 * Return an array of class names in LoaderInfo object.
	 * 
	 * @param	data		Associated LoaderInfo object or a ByteArray, which contains swf data.
	 * 
	 * @param	extended	If false, function returns only classes & interfaces.
	 * 						If true, function return all visible definitions (classes, interfaces, functions, namespaces, variables, constants, etc.).
	 * 						Extended mode is slightly slower than a regular search.
	 * 
	 * @param	linkedOnly	If true, function returns only linked classes (objects with linkage), MUCH faster than regular or extended search.
	 * 						This mode is preferable if you need only graphic resources (sprites, bitmaps, fonts, etc.).
	 * 						NB: "extended" parameter will be ignored if this argument is true.	
	 */ 
	public function getDefinitionNames(data:Object, extended:Boolean = false, linkedOnly:Boolean = false):Array {
		var bytes:ByteArray;
		
		if (data is LoaderInfo) {
			bytes = (data as LoaderInfo).bytes;
		} else if (data is ByteArray) {
			bytes = data as ByteArray;
		} else throw new ArgumentError('Error #1001: The specified data is invalid');
		
		var position:uint = bytes.position;
		var finder:Finder = new Finder(bytes);
		bytes.position = position;
		return finder.getDefinitionNames(extended, linkedOnly);
	}
}

import flash.utils.ByteArray;
import flash.utils.Endian;
import flash.geom.Rectangle;
import flash.system.ApplicationDomain;
import ru.goodcore.utils.traceByteArray;
import ru.goodcore.utils.StringUtils;

/**
 * @private
 */
internal class Finder {

	public function Finder(bytes:ByteArray) {
		super();
		this._data = new SWFByteArray(bytes);
	}

	/**
	 * @private
	 */
	private var _data:SWFByteArray;

	/**
	 * @private
	 */
	private var _stringTable:Array;

	/**
	 * @private
	 */
	private var _namespaceTable:Array;

	/**
	 * @private
	 */
	private var _multinameTable:Array;

	public function getDefinitionNames(extended:Boolean, linkedOnly:Boolean):Array {
		var definitions:	Array = new Array();
		var tag:			uint;
		var tagId:			uint;
		var minorVersion:	uint;
		var majorVersion:	uint;
		var name:			String;
		var index:			int;

		var position:		uint;
		var badata:			ByteArray = this._data;
		var swfTag:			SWFTag;

		while (this._data.bytesAvailable) {
			position = this._data.position;

			swfTag = new SWFTag(this._data);
			tagId = swfTag.tagId;

			trace(swfTag.toString());

			this._data.position = position + swfTag.headerLength;

			if (linkedOnly) {

				switch (tagId) {
					case 76: // SymbolClass
						var count:uint = this._data.readUnsignedShort();

						while (count--) {
							this._data.readUnsignedShort(); // Object ID
							name = this._data.readString();
							index = name.lastIndexOf('.');
							if (index >= 0) name = name.substr(0, index) + '::' + name.substr(index + 1); // Fast. Simple. Cheat ;)
							definitions.push(name);
						}

						break;
				}

			} else {
				switch (tagId) {
					case 72:
					case 82:
						if (tagId == 82) {
							this._data.position += 4; // skip Flags UI32
							//trace( traceByteArray(this._data, this._data.position, 40) );
							var ii:String = this._data.readString(); // identifier
						}

						minorVersion = this._data.readUnsignedShort();
						majorVersion = this._data.readUnsignedShort();
						if (minorVersion == 0x0010 && majorVersion == 0x002E) 
							definitions.push.apply(definitions, this.getDefinitionNamesInTag(extended));
					break;
				}
			}

			this._data.position = position + swfTag.totalLength;
		}

		return definitions;
	}

	
	/**
	 * @private
	 */
	private function getDefinitionNamesInTag(extended:Boolean):Array {
		var badata:ByteArray = this._data;
		var classesOnly:Boolean = !extended;
		var count:int;
		var kind:uint;
		var id:uint;
		var flags:uint;
		var counter:uint;
		var ns:uint;
		var names:Array = new Array();
		
		var stringTable:	Array = this._stringTable = new Array();
		var namespaceTable:	Array = this._namespaceTable = new Array();
		var multinameTable:	Array = this._multinameTable = new Array();
		var nsSetTable:		Array = new Array();
		
		// int table
		count = this._data.readASInt() - 1;
		
		while (count > 0 && count--) {
			this._data.readASInt();
		}
		
		// uint table
		count = this._data.readASInt() - 1;
		
		while (count > 0 && count--) {
			this._data.readASInt();
		}

		// Double table
		count = this._data.readASInt() - 1;
		
		while (count > 0 && count--) {
			this._data.readDouble();
		}
		
		// String table
		count = this._data.readASInt()-1;
		id = 1;
		
		while (count > 0 && count--) {
			this._stringTable[id] = this._data.readUTFBytes(this._data.readASInt());
			id++;
		}
		
		// Namespace table
		count = this._data.readASInt() - 1;
		id = 1;
		
		while (count > 0 && count--) {
			kind = this._data.readUnsignedByte();
			ns = this._data.readASInt();
			//if (kind == 0x16) 
				this._namespaceTable[id] = ns; // only public
			id++;
		}
		
		// NsSet table
		count = this._data.readASInt() - 1;
		id = 1;
		
		while (count > 0 && count--) {
			 counter = this._data.readUnsignedByte();
			 while (counter--) {
			 	nsSetTable[id] = this._data.readASInt();
			 	id++;
			 }
		}
		
		// Multiname table
		count = this._data.readASInt() - 1;
		id = 1;
		
		while (count > 0 && count--) {
            kind = this._data.readASInt();
            
            switch (kind) {
                case 0x07:
                case 0x0D:
                	ns =  this._data.readASInt();
                    this._multinameTable[id] = [ns, this._data.readASInt()];
                break;    
                case 0x0F:
                case 0x10:
                    this._multinameTable[id] = [0, this._stringTable[this._data.readASInt()]];
                break;    
                case 0x11:
                case 0x12:
                break;    
                case 0x09:
                case 0x0E:
                    this._multinameTable[id] = [0, this._stringTable[this._data.readASInt()]];
                    this._data.readASInt();
                break;    
                case 0x1B:
                case 0x1C:
                    this._data.readASInt();
                break;    
            }
            
            id++;
		}
		
		// Method table
		count = this._data.readASInt();

		while (count > 0 && count--) {
			var paramsCount:int = this._data.readASInt();
			counter = paramsCount;
			this._data.readASInt();
			while (counter--) this._data.readASInt();
			this._data.readASInt();
			flags = this._data.readUnsignedByte();
			
			if (flags & 0x08) {
				counter = this._data.readASInt();
				
				while (counter--) {
					this._data.readASInt();
					this._data.readASInt();
				}
			}
			
			if (flags & 0x80) {
				counter = paramsCount;
				while (counter--) this._data.readASInt();
			}
		}

		// Metadata table
		count = this._data.readASInt();

		while (count > 0 && count--) {
			this._data.readASInt();
			counter = this._data.readASInt();
			
			while (counter--) {
				this._data.readASInt();
				this._data.readASInt();
			}
		}

		// Instance table
		count = this._data.readASInt();
		var classCount:uint = count;
		var name:String;

		while (count > 0 && count--) {
			id = this._data.readASInt();
			this._data.readASInt();
            flags = this._data.readUnsignedByte();
            if (flags & 0x08) ns = this._data.readASInt();
            counter = this._data.readASInt();
            while (counter--) this._data.readASInt();
            this._data.readASInt(); // iinit
            this.readTraits();
            
            if (classesOnly) {
            	name = this.getName(id);
            	if (name) names.push(name);
            }
		}
		
		if (classesOnly) return names;
		
		// Class table
		count = classCount;
		
		while (count && count--) {
			this._data.readASInt(); // cinit
			this.readTraits();
		}
		
		// Script table
		count = this._data.readASInt();
		var traits:Array;
		
		while (count && count--) {
			this._data.readASInt(); // init
			traits = this.readTraits(true);
			if (traits.length) names.push.apply(names, traits);
		}

		return names;
	}
	
	/**
	 * @private
	 */
	private function readTraits(buildNames:Boolean = false):Array {
		var kind:uint;
		var counter:uint;
        var ns:uint;
        var id:uint;
        var traitCount:uint = this._data.readASInt();
        var names:Array;
        var name:String;
        if (buildNames) names = [];

        while (traitCount--) {
            id = this._data.readASInt(); // name
            kind = this._data.readUnsignedByte();
            var upperBits:uint = kind >> 4;
            var lowerBits:uint = kind & 0xF;
            this._data.readASInt();
            this._data.readASInt();
            
            switch (lowerBits) {
                case 0x00:
                case 0x06:
                    if (this._data.readASInt()) this._data.readASInt();
                    break;
            }

        	if (buildNames) {
				name = this.getName(id);
				if (name) names.push(name);
        	}
            
            if (upperBits & 0x04) {
                counter = this._data.readASInt();
                while (counter--) this._data.readASInt();
            }
        }
		
		return names;
	}
	
	/**
	 * @private
	 */
	private function getName(id:uint):String {
		var mn:Array = this._multinameTable[id];
		var ns:uint = mn[0] as uint;
		if (!(ns in this._namespaceTable)) return null;
		var nsName:String = this._stringTable[this._namespaceTable[ns] as uint] as String;
		var name:String = this._stringTable[mn[1] as uint] as String;
		if (nsName) name = nsName + '::' + name;
		return name;
	}

}

internal class SWFTag {

	public static const TAG_NAMES:Object = {
		  0 : 'End',
		  1 : 'ShowFrame',
		  2 : 'DefineShape',
		  9 : 'SetBackgroundColor',
		 20 : 'DefineBitsLossless',
		 21 : 'DefineBitsJPEG2',
		 26 : 'PlaceObject2',
		 36 : 'DefineBitsLossless2',
		 37 : 'DefineEditText',
		 39 : 'DefineSprite',
		 41 : 'ProductInfo',
		 43 : 'FrameLabel',
		 56 : 'ExportAssets',
		 63 : 'DebugId ?',
		 64 : 'EnableDebugger2',
		 65 : 'ScriptLimits',
		 69 : 'FileAttributes',
		 73 : 'DefineFontAlignZones',
		 74 : 'CSMTextSettings',
		 75 : 'DefineFont3',
		 76 : 'SymbolClass',
		 77 : 'Metadata',		 
		 82 : 'DoABC',
		 86 : 'DefineSceneAndFrameLabelData',
		 88 : 'DefineFontName',
		 90 : 'DefineBitsJPEG4',
		 91 : 'DefineFont4'
		 
	};

	public function SWFTag(ba:ByteArray):void {
		this.body.endian = Endian.LITTLE_ENDIAN;
		var position:uint = ba.position;
		this.tag = ba.readUnsignedShort();
		this.tagId = this.tag >> 6;
		this.contentLength = this.tag & 0x3F;

		if (this.contentLength == 0x3F) {
			this.contentLength = ba.readUnsignedInt();
			this.headerLength = 6;
		} else {
			this.headerLength = 2;
		}

		this.totalLength = this.headerLength + this.contentLength;

		ba.position = position;
		ba.readBytes(this.body, 0, this.totalLength);
		ba.position = position;
	}

	public var tag:				uint;
	public var tagId:			uint;
	public var body:			ByteArray = new ByteArray();
	public var headerLength:	uint;
	public var contentLength:	uint;
	public var totalLength:		uint;

	/**
	 * @private 
	 */	
	private var _bitIndex:uint = 0;

	//-------------------------------------------------------------------------
	//
	//   Private methosd
	//
	//-------------------------------------------------------------------------
	/**
	 * @private
	 */
	private function readRect():Rectangle {
		var position:	uint = this.body.position;
		var byte:		uint = this.body[position];
		var bits:		uint = byte >> 3;
		var xMin:		Number = this.readBits(bits, 5) / 20;
		var xMax:		Number = this.readBits(bits) / 20;
		var yMin:		Number = this.readBits(bits) / 20;
		var yMax:		Number = this.readBits(bits) / 20;

		this.body.position = position + Math.ceil((bits * 4 - 3) / 8) + 1;

		return new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin);
	}

	/**
	 * @private
	 */
	private function readBits(length:uint, start:int = -1):Number {
		if (start < 0) start = this._bitIndex;
		this._bitIndex = start;
		var byte:					uint = this.body[this.body.position];
		var out:					Number = 0;
		var shift:					Number = 0;
		var currentByteBitsLeft:	uint = 8 - start;
		var bitsLeft:				Number = length - currentByteBitsLeft;

		if (bitsLeft > 0) {
			this.body.position++;
			out = this.readBits(bitsLeft, 0) | (byte & (1 << currentByteBitsLeft) - 1) << bitsLeft;
		} else {
			out = (byte >> (8 - length - start)) & (1 << length) - 1;
			this._bitIndex = (start + length) % 8;
			if (start + length > 7) this.body.position++;
		}

		return out;
	}

	private function readMatrix(ba:ByteArray):void {
		var byte:uint = ba.readUnsignedByte();
		trace('[MATRIX: ]');
		var HasScale:Boolean = (byte & 0x80) > 0;

		trace(  'HasScale: ' + HasScale);
		
		var NScaleBits:	uint = 0;
		var ScaleX:		uint = 0;
		var ScaleY:		uint = 0;

		if (HasScale) {
			NScaleBits = (byte & 0x7C) >> 2
			trace(  'NScaleBits: ' + NScaleBits );
			//ScaleX = 
			
		}
		
		
		
	}

	//-------------------------------------------------------------------------
	//
	//   Public methosd
	//
	//-------------------------------------------------------------------------

	public function toString(nestedLevel:uint = 0):String {
		this.body.position = this.headerLength;

		var result:	String = '';
		var name:	String = TAG_NAMES[this.tagId] || 'Unknown';

		var printOffset:	String = '';
		while (printOffset.length < nestedLevel * 4) {
			printOffset += ' ';
		}

		nestedLevel++;

		result += printOffset + '- ' + name + ' (' + this.tagId.toString() + ')' + ', 0x' + StringUtils.setNumberLen(this.tag, 4, 16).toUpperCase();
		result += ', contentLength = ' + this.contentLength + ', totalLength = ' + this.totalLength;
		result += '\n' + traceByteArray(this.body);

		var symbolId:	uint;
		var flags:		uint;
		var target:		String;

		switch (this.tagId) {
			case 2: // DefineShape
				symbolId = this.body.readShort(); // Shape ID
				result += '\n' + printOffset + '  [Shape ID]:\t' + symbolId;

				var rect:Rectangle = this.readRect();
				result += '\n' + printOffset + '  [RECT]:\t' + rect.toString();

				var fillStyleCount:int = this.body.readByte();
				result += '\n' + printOffset + '  [FillStyleCount]:\t' + fillStyleCount;
				
				
				var fillStyleType:	uint;
				var tmp:			String;

				while (fillStyleCount-- > 0) {
					fillStyleType = this.body.readByte();
					result += '\n' + printOffset + '     [FillStyleType]:\t' + fillStyleType + '(0x' + fillStyleType.toString(16) + ')';

					switch (fillStyleType) {
						case 0x40:
						case 0x41:
						case 0x42:
						case 0x43:
							result += '\n' + printOffset + '     [BitmapId]:\t' + this.body.readShort();

							var matrixByte:uint = this.body.readUnsignedByte();
							target = StringUtils.setNumberLen(matrixByte, 8, 2);
							result += '\n' + printOffset + '     [MATRIX]:\t' + target;

							var HasScale:Boolean = (matrixByte & 0x80) > 0;
							result += '\n' + printOffset + '         [HasScale]:\t' + HasScale;

							

							if (HasScale) {
								var NScaleBits:uint = parseInt(target.substr(1, 5), 2);
								result += '\n' + printOffset + '         [NScaleBits]:\t' + NScaleBits;
								
								var hasBitsCount:uint = 2;
								var leftBitsCount:int = NScaleBits - hasBitsCount;
								target = target.substr(-2, 2);
								
								while (leftBitsCount > 0) {
									matrixByte = this.body.readUnsignedByte();
									target += StringUtils.setNumberLen(matrixByte, 8, 2);
									leftBitsCount -= 8;
								}
								
								tmp = target.substr(0, NScaleBits - 16) // Low 16 is after decimal point
								
								var ScaleX:int = parseInt(tmp, 2);
								tmp += '.' + target.substr(NScaleBits - 16, 16);
								result += '\n' + printOffset + '         [ScaleX]:\t' + ScaleX + '(' + tmp + ')';
								
								target = target.substr(NScaleBits);
								
								hasBitsCount = target.length;
								leftBitsCount = NScaleBits - hasBitsCount;
								
								while (leftBitsCount > 0) {
									matrixByte = this.body.readUnsignedByte();
									target += StringUtils.setNumberLen(matrixByte, 8, 2);
									leftBitsCount -= 8;
								}

								tmp = target.substr(0, NScaleBits - 16) // Low 16 is after decimal point

								var ScaleY:int = parseInt(tmp, 2);
								tmp += '.' + target.substr(NScaleBits - 16, 16);
								
								result += '\n' + printOffset + '         [ScaleY]:\t' + ScaleY + '(' + tmp + ')';

								target = target.substr(NScaleBits);
								hasBitsCount = target.length;
							}
							
							var HasRotate:Boolean = target.substr(0, 1) == '1';
							target = target.substr(1);
							result += '\n' + printOffset + '         [HasRotate]:\t' + HasRotate;
							
							if (HasRotate) {
								
							}

							var NTranslateBits:uint = parseInt(target.substr(0, 5), 2);
							result += '\n' + printOffset + '         [NTranslateBits]:\t' + NTranslateBits + ' (' + target + ')';
							target = target.substr(5);

							if (NTranslateBits) {
								hasBitsCount = target.length;
								leftBitsCount = NTranslateBits - hasBitsCount;

								while (leftBitsCount > 0) {
									byte = this.body.readUnsignedByte();
									target += StringUtils.setNumberLen(byte, 8, 2);
									leftBitsCount -= 8;
								}

								tmp = target.substr(0, NTranslateBits);
								var TranslateX:Number = this.getSBNumber(tmp);
								result += '\n' + printOffset + '         [TranslateX]:\t' + TranslateX + ' (' + TranslateX / 20 + ' px)';
								target = target.substr(NTranslateBits);

								hasBitsCount = target.length;
								leftBitsCount = NTranslateBits - hasBitsCount;

								while (leftBitsCount > 0) {
									byte = this.body.readUnsignedByte();
									target += StringUtils.setNumberLen(byte, 8, 2);
									leftBitsCount -= 8;
								}

								tmp = target.substr(0, NTranslateBits);
								var TranslateY:Number = this.getSBNumber(tmp);
								result += '\n' + printOffset + '         [TranslateY]:\t' + TranslateY + ' (' + TranslateY / 20 + ' px)';
								target = target.substr(NTranslateBits);
							}
							
							break;
					}

					
				}

				var lineStyleCount:int = this.body.readByte();
				result += '\n' + printOffset + '  [LineStyleCount]:\t' + lineStyleCount;
				
				while (lineStyleCount-- > 0) {
					
				}
				
				var byte:uint = this.body.readUnsignedByte();
				var NumBits:	uint;
				
				var NumFillBits:uint = (byte & 0xF0) >> 4;
				var NumLineBits:uint = byte & 0x0F;
				
				result += '\n' + printOffset + '  [NumFillBits/NumLineBits]:\t' + StringUtils.setNumberLen(byte, 8, 2) + ' (0x' + byte.toString(16).toUpperCase() + ')';
				
				result += '\n' + printOffset + '  [NumFillBits]:\t' + NumFillBits;
				result += '\n' + printOffset + '  [NumLineBits]:\t' + NumLineBits;

				target = '';
				var pos:	uint = 0;

				while ( true ) {

					if (this.body.bytesAvailable)
						byte = this.body.readUnsignedByte();

					target += StringUtils.setNumberLen(byte, 8, 2);

					result += '\n' + printOffset + '  [SHAPERECORD]:\t' + target + ' (0x' + byte.toString(16).toUpperCase() + ')';

					var TypeFlag:uint = parseInt(target.substr(pos++, 1));

					if (TypeFlag == 0) {

						if (target.substr(1, 5) == '00000') { // ENDSHAPERECORD
							result += '\n' + printOffset + '      [ENDSHAPERECORD]:' + TypeFlag;
							result += '\n' + printOffset + '          [TypeFlag]:\t' + TypeFlag;
							result += '\n' + printOffset + '          [EndOfShape]:\t' + target.substr(1, 5);
							break;
						}

						else {
							result += '\n' + printOffset + '      [STYLECHANGERECORD]:';
							result += '\n' + printOffset + '          [TypeFlag]:\t\t' + TypeFlag;

							var StateNewStyles:uint = parseInt(target.substr(pos++, 1));
							result += '\n' + printOffset + '          [StateNewStyles]:\t' + Boolean(StateNewStyles > 0);

							var StateLineStyle:uint = parseInt(target.substr(pos++, 1));
							result += '\n' + printOffset + '          [StateLineStyle]:\t' + Boolean(StateLineStyle > 0);

							var StateFillStyle1:uint = parseInt(target.substr(pos++, 1));
							result += '\n' + printOffset + '          [StateFillStyle1]:\t' + Boolean(StateFillStyle1 > 0);

							var StateFillStyle0:uint = parseInt(target.substr(pos++, 1));
							result += '\n' + printOffset + '          [StateFillStyle0]:\t' + Boolean(StateFillStyle0 > 0);

							var StateMoveTo:uint = parseInt(target.substr(pos++, 1));
							result += '\n' + printOffset + '          [StateMoveTo]:\t' + Boolean(StateMoveTo > 0);

							target = target.substr(pos);
							pos = 0;

							if (StateMoveTo) {

								var MoveBits:	uint = 5;

								hasBitsCount = target.length;

								while (hasBitsCount < MoveBits) {
									byte = this.body.readUnsignedByte();
									target += StringUtils.setNumberLen(byte, 8, 2);
									hasBitsCount += 8;
								}

								NumBits = parseInt(target.substr(0, MoveBits), 2);
								target = target.substr(MoveBits);

								result += '\n' + printOffset + '          [MoveBits]:\t\t' + NumBits;

								hasBitsCount = target.length;
								leftBitsCount = NumBits - hasBitsCount;

								while (leftBitsCount > 0) {
									byte = this.body.readUnsignedByte();
									target += StringUtils.setNumberLen(byte, 8, 2);
									leftBitsCount -= 8;
								}

								tmp = target.substr(0, NumBits) //
								var MoveDeltaX:Number = this.getSBNumber(tmp);
								result += '\n' + printOffset + '          [MoveDeltaX]:\t\t' + MoveDeltaX + ' (' + MoveDeltaX / 20 + ' px)';

								target = target.substr(NumBits);

								hasBitsCount = target.length;
								leftBitsCount = NumBits - hasBitsCount;

								while (leftBitsCount > 0) {
									byte = this.body.readUnsignedByte();
									target += StringUtils.setNumberLen(byte, 8, 2);
									leftBitsCount -= 8;
								}

								tmp = target.substr(0, NumBits); //
								var MoveDeltaY:Number = parseInt(tmp, 2);
								result += '\n' + printOffset + '          [MoveDeltaY]:\t\t' + MoveDeltaY + ' (' + MoveDeltaY / 20 + ' px)';

								target = target.substr(NumBits);
								pos = 0;
							}

							if (StateFillStyle0) {

								hasBitsCount = target.length;

								while (hasBitsCount < NumFillBits) {
									byte = this.body.readUnsignedByte();
									target += StringUtils.setNumberLen(byte, 8, 2);
									hasBitsCount += 8;
								}

								var fillIndex:String = target.substr(0, NumFillBits);

								//target = target.substr(NumFillBits);

								result += '\n' + printOffset + '          [FillStyle0]:\t\t' + parseInt(fillIndex, 2);
								target = target.substr(NumFillBits);
								pos = 0;
							}

							if (StateFillStyle1) {

								hasBitsCount = target.length;

								while (hasBitsCount < NumFillBits) {
									byte = this.body.readUnsignedByte();
									target += StringUtils.setNumberLen(byte, 8, 2);
									hasBitsCount += 8;
								}

								fillIndex = target.substr(0, NumFillBits);

								//target = target.substr(NumFillBits);

								result += '\n' + printOffset + '          [FillStyle1]:\t\t' + parseInt(fillIndex, 2);
								target = target.substr(NumFillBits);
								pos = 0;
							}
							
						}
					} else {
						if (target.substr(1, 1) == '1') { // STRAIGHTEDGERECORD
							result += '\n' + printOffset + '      [STRAIGHTEDGERECORD]:';
							result += '\n' + printOffset + '          [TypeFlag]:\t' + TypeFlag;

							NumBits = parseInt( target.substr(2, 4), 2 );
							result += '\n' + printOffset + '          [NumBits]:\t' + NumBits + ' (real length = ' + (NumBits + 2) + ')';

							NumBits += 2;

							var GeneralLineFlag:Boolean = target.substr(6, 1) == '1';
							result += '\n' + printOffset + '          [GeneralLineFlag]:\t' + GeneralLineFlag;

							var VertLineFlag:Boolean = target.substr(7, 1) == '1';
							result += '\n' + printOffset + '          [VertLineFlag]:\t' + VertLineFlag;
							
							target = target.substr(8);

							hasBitsCount = target.length;
							leftBitsCount = NumBits - hasBitsCount;

							while (leftBitsCount > 0) {
								byte = this.body.readUnsignedByte();
								target += StringUtils.setNumberLen(byte, 8, 2);
								leftBitsCount -= 8;
							}

							tmp = target.substr(0, NumBits); //

							var Delta:Number = this.getSBNumber(tmp);

							if (GeneralLineFlag || !VertLineFlag) {
								// Horizontal delta
								result += '\n' + printOffset + '          [DeltaX]:\t' + Delta + ' (' + Delta / 20 + ' px)';
							} else {
								// Vertical delta
								result += '\n' + printOffset + '          [DeltaY]:\t' + Delta + ' (' + Delta / 20 + ' px)';
							}

							target = target.substr(NumBits);
							pos = 0;
							
							if (GeneralLineFlag) {
								hasBitsCount = target.length;
								leftBitsCount = NumBits - hasBitsCount;

								while (leftBitsCount > 0) {
									byte = this.body.readUnsignedByte();
									target += StringUtils.setNumberLen(byte, 8, 2);
									leftBitsCount -= 8;
								}

								tmp = target.substr(0, NumBits);
								var DeltaY:Number = this.getSBNumber(tmp);
	
								result += '\n' + printOffset + '          [DeltaY]:\t' + DeltaY + ' (' + DeltaY / 20 + ' px)';
								target = target.substr(NumBits);
								pos = 0;
							}

						} else {
							result += '\n' + printOffset + '      [CURVEDEDGERECORD]:';
							result += '\n' + printOffset + '          [TypeFlag]:\t' + TypeFlag;
							NumBits = parseInt( target.substr(2, 4), 2 );
							result += '\n' + printOffset + '          [NumBits]:\t' + NumBits;

							target = target.substr(NumBits);
							pos = 0;
						}
					}
					
				}
				
				

				break;

			case 20: // DefineBitsLossless
			case 36: // DefineBitsLossless2
				symbolId = this.body.readShort(); // Character ID
				result += '\n' + printOffset + '  [Character ID]: ' + symbolId;
				break;

			case 26: // PlaceObject2
				flags = this.body.readByte(); // Flags
				result += '\n' + printOffset + '  [HasClipActions]:\t\t' + 		((flags & (1 << 7)) > 0 ? 'true' : 'false');
				result += '\n' + printOffset + '  [HasClipDepth]:\t\t' + 		((flags & (1 << 6)) > 0 ? 'true' : 'false');
				result += '\n' + printOffset + '  [HasName]:\t\t' + 			((flags & (1 << 5)) > 0 ? 'true' : 'false');
				result += '\n' + printOffset + '  [HasRatio]:\t\t' + 			((flags & (1 << 4)) > 0 ? 'true' : 'false');
				result += '\n' + printOffset + '  [HasColorTransform]:\t' + 	((flags & (1 << 3)) > 0 ? 'true' : 'false');
				result += '\n' + printOffset + '  [HasMatrix]:\t\t' + 			((flags & (1 << 2)) > 0 ? 'true' : 'false');
				result += '\n' + printOffset + '  [HasCharacter]:\t\t' + 		((flags & (1 << 1)) > 0 ? 'true' : 'false');
				result += '\n' + printOffset + '  [Move]:\t\t\t' + 				((flags & 1       ) > 0 ? 'true' : 'false');
				result += '\n' + printOffset + '  [Depth]:\t\t\t' + this.body.readShort();

				if (flags & (1 << 1)) {
					result += '\n' + printOffset + '  [CharacterId]:\t\t' + this.body.readShort();
				}

				break;

			case 39: // DefineSprite
				symbolId = this.body.readUnsignedShort(); // Sprite ID
				var numFrames:uint =  this.body.readUnsignedShort(); // Number of frames in sprite

				result += '\n' + printOffset + '  Sprite ID: ' + symbolId.toString() + ', numFrames: ' + numFrames.toString();
				var controlTag:	SWFTag;

				var position:uint = this.body.position;

				while ( this.body.bytesAvailable > 0 ) { // End Tag
					controlTag = new SWFTag(this.body)
					result += '\n' + controlTag.toString(nestedLevel);
					position += controlTag.totalLength;
					this.body.position = position;
				}
				break;
		}

		result += '\n';

		this.body.position = this.headerLength;

		return result;
	}
	
	private function getSBNumber(value:String):Number {
		var result:		Number;

		var isNegative:	Boolean = value.substr(0, 1) == '1';
		var body:		String = value.substr(0);

		if ( !isNegative ) {
			result = parseInt(body, 2);
		} else {
			var aaa:String = body.replace(/0/g, '2');
			aaa = aaa.replace(/1/g, '0');
			aaa = aaa.replace(/2/g, '1');
			result = -(parseInt(aaa, 2) + 1);
		}

		return result;
	}


}


internal class SWFByteArray extends ByteArray {
	
	/**
	 * @private
	 */
	private static const TAG_SWF:String = 'FWS';
	
	/**
	 * @private
	 */
	private static const TAG_SWF_COMPRESSED:String = 'CWS';
	
	public function SWFByteArray(data:ByteArray=null):void {
		super();
		super.endian = Endian.LITTLE_ENDIAN;
		var endian:String;
		var tag:String;
		
		if (data) {
			endian = data.endian;
			data.endian = Endian.LITTLE_ENDIAN;
			
			if (data.bytesAvailable > 26) {
				tag = data.readUTFBytes(3);
				
				if (tag == SWFByteArray.TAG_SWF || tag == SWFByteArray.TAG_SWF_COMPRESSED) {
					this._version = data.readUnsignedByte();
					data.readUnsignedInt();
					data.readBytes(this);
					if (tag == SWFByteArray.TAG_SWF_COMPRESSED) super.uncompress();
				} else throw new ArgumentError('Error #2124: Loaded file is an unknown type.');
				
				this.readHeader();
			}
			
			data.endian = endian;
		}
	}
		
	/**
	 * @private
	 */
	private var _bitIndex:uint;
	
	/**
	 * @private
	 */
	private var _version:uint;
	
	public function get version():uint {
		return this._version;
	}
	
	/**
	 * @private
	 */
	private var _frameRate:Number;
	
	public function get frameRate():Number {
		return this._frameRate;	
	}
	
	/**
	 * @private
	 */
	private var _rect:Rectangle;
	
	public function get rect():Rectangle {
		return this._rect;
	}

	/**
	 * @private
	 * @param bytesHexString
	 * @param ba
	 */
	public function writeBytesFromString(bytesHexString:String, ba:ByteArray=null):void {
		bytesHexString = bytesHexString.replace(/\s/g, '');

		var length:uint = bytesHexString.length;
		
		if (!ba)
			ba = this;

		for (var i:uint = 0; i < length; i += 2) {
			var hexByte:String = bytesHexString.substr(i, 2);
			var byte:uint = parseInt(hexByte, 16);
			ba.writeByte(byte);
		}
	}
	
	public function readRect():Rectangle {
		var pos:uint = super.position;
		var byte:uint = this[pos];
		var bits:uint = byte >> 3;
		var xMin:Number = this.readBits(bits, 5) / 20;
		var xMax:Number = this.readBits(bits) / 20;
		var yMin:Number = this.readBits(bits) / 20;
		var yMax:Number = this.readBits(bits) / 20;
		super.position = pos + Math.ceil(((bits * 4) - 3) / 8) + 1;
		return new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin);
	}
	
	public function readBits(length:uint, start:int = -1):Number {
		if (start < 0) start = this._bitIndex;
		this._bitIndex = start;
		var byte:uint = this[super.position];
		var out:Number = 0;
		var shift:Number = 0;
		var currentByteBitsLeft:uint = 8 - start;
		var bitsLeft:Number = length - currentByteBitsLeft;
		
		if (bitsLeft > 0) {
			super.position++;
			out = this.readBits(bitsLeft, 0) | ((byte & ((1 << currentByteBitsLeft) - 1)) << (bitsLeft));
		} else {
			out = (byte >> (8 - length - start)) & ((1 << length) - 1);
			this._bitIndex = (start + length) % 8;
			if (start + length > 7) super.position++;
		}
		
		return out;
	}
	
    public function readASInt():int {
		var result:uint = 0;
		var i:uint = 0, byte:uint;
		do {
			byte = super.readUnsignedByte();
			result |= ( byte & 0x7F ) << ( i*7 );
			i+=1;
		} while ( byte & 1<<7 );
		return result;	        
    }

	public function readString():String {
		var i:uint = super.position;
		while (this[i] && (i+=1)) void;
		var str:String = super.readUTFBytes(i - super.position);
		super.position = i+1; 
		return str;
    }

	public function traceArray(array:ByteArray):String { // for debug
		var out:String = '';
		var pos:uint = array.position;
		var i:uint = 0;
		array.position = 0;

		while (array.bytesAvailable) {
			var str:String = array.readUnsignedByte().toString(16).toUpperCase();
			str = str.length < 2 ? '0'+str : str;
			out += str+' ';
		}
		
		array.position = pos;
		return out;
	}

	/**
	 * @private
	 */
	private function readFrameRate():void {
		if (this._version < 8) {
			this._frameRate = super.readUnsignedShort();
		} else {
			var fixed:Number = super.readUnsignedByte() / 0xFF;
			this._frameRate = super.readUnsignedByte() + fixed;
		}
	}
	
	/**
	 * @private
	 */
	private function readHeader():void {
		this._rect = this.readRect();
		this.readFrameRate();		
		super.readShort(); // num of frames
	}
}